
package com.gome.utils;


import com.gome.exception.GlobalException;
import com.gome.exception.GlobalExceptionDatas;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.apache.log4j.Logger;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.spec.SecretKeySpec;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.security.MessageDigest;
import java.security.Security;

/**
 * 加解密工具类.
 * <p>
 * 包含对字符串、文件的加密、解密处理，加解密算法采用内置固定的算法.
 * </p>
 *
 * @author fasoal
 */
public final class EncryptUtils {
	/** 采用固定的加解密算法. */
	private static final String ALGORITHM = "RC4";

	/** MD5摘要算法的名称. */
	private static final String MD5 = "md5";

	/** 读取数据块缓冲池大小. */
	private static final int BUFFER_SIZE = 2048;

	/** 处理Key的初始值. */
	private static final byte INIT_KEY = 8;

	/** 加密算法提供者名称. */
	private static final String PROVIDER_NAME = "BC";

	/** 日志输出对象. */
	private static Logger log = Logger.getLogger(EncryptUtils.class);

	static {
		// 将Bouncy Castle提供的加密算法加入JAVA安全环境.
		Security.addProvider(new BouncyCastleProvider());
	}

	/**
	 * 私有构造函数，防止对象被实例化.
	 */
	private EncryptUtils() {

	}

	/**
	 * 对密钥进行异或处理
	 *
	 * @param key 要处理的Key
	 * @return 处理后的密钥值.
	 */
	private static byte[] processKey(byte[] key) {
		byte[] result = new byte[key.length];

		// 对密钥的每一位进行异或处理.
		for (int i = 0; i < key.length; i++) {
			result[i] = (byte) (key[i] ^ INIT_KEY);
		}
		return result;
	}

	/**
	 * 构建Cipher对象.
	 *
	 * @param key 密钥信息
	 * @param algorithm 加解密算法.
	 * @param mode 加解密模式.
	 * @return 构建好的Cipher对象.
	 * @throws Exception 构建Cipher对象时产生的异常信息.
	 */
	private static Cipher getCipher(byte[] key, String algorithm, int mode) throws Exception {
		// 构建密钥对象.
		SecretKeySpec skeySpec = new SecretKeySpec(key, algorithm);

		Cipher cipher = Cipher.getInstance(algorithm, PROVIDER_NAME);

		// 设置密钥的算法及加解密模式.
		cipher.init(mode, skeySpec);

		return cipher;
	}

	/**
	 * 对指定的数据进行加密操作.
	 *
	 * @param key 加密密钥
	 * @param data 需要进行加密的数据
	 * @return 加密后的数据
	 * @throws Exception 加密过程中产生的异常
	 */
	public static byte[] encrypt(byte[] key, byte[] data) {
		try {
			// 构建密钥对象，采用预置的加密算法，密钥模式为加密模式.
			Cipher cipher = getCipher(processKey(key), ALGORITHM, Cipher.ENCRYPT_MODE);

			// 执行加密操作.
			return cipher.doFinal(data);
		}
		catch (Exception e) {
			// 异常处理，将异常转换成业务类异常抛出
			String errCode = GlobalExceptionDatas.COMM_ERR_ECRYPT;

			throw new GlobalException(errCode);
		}
	}

	/**
	 * 对指定的数据进行解密操作。
	 *
	 * @param key 解密密钥
	 * @param data 需要进行解密的数据
	 * @return 解密后的数据
	 * @throws Exception 解密过程中产生的异常
	 */
	public static byte[] decrypt(byte[] key, byte[] data) {
		try {
			// 构建密钥对象，采用预置的加密算法，密钥模式为解密模式.
			Cipher cipher = getCipher(processKey(key), ALGORITHM, Cipher.DECRYPT_MODE);

			// 执行解密操作
			return cipher.doFinal(data);
		}
		catch (Exception e) {
			// 异常处理，将异常转换成业务类异常抛出
			String errCode = GlobalExceptionDatas.COMM_ERR_DECRYPT;

			throw new GlobalException(errCode);
		}
	}

	/**
	 * 对文件进行加密处理
	 *
	 * @param key 密钥
	 * @param srcFile 加密的文件
	 * @param destFile 加密后的文件
	 * @throws Exception 文件加密过程中产生的异常信息
	 */
	public static void encryptFile(byte[] key, String srcFile, String destFile) {
		// 要加密的文件输入流.
		FileInputStream fis = null;

		// 加密后的文件输出流
		CipherOutputStream cos = null;

		try {
			// 构建密钥对象，采用预置的加密算法，密钥模式为加密模式.
			Cipher cipher = getCipher(processKey(key), ALGORITHM, Cipher.ENCRYPT_MODE);

			fis = new FileInputStream(srcFile);

			// 构建加密输出流.
			cos = new CipherOutputStream(new FileOutputStream(destFile), cipher);

			// 执行文件加密操作.
			byte[] b = new byte[BUFFER_SIZE];
			int len = -1;
			while ((len = fis.read(b)) != -1) {
				cos.write(b, 0, len);
			}
			cos.flush();
		}
		catch (Exception e) {
			// 异常处理，将异常转换成业务类异常抛出
			String errCode = GlobalExceptionDatas.COMM_ERR_ECRYPT_FILE;
			throw new GlobalException(errCode, srcFile, destFile);
		}
		finally {

			// 对打开的文件资源进行释操作.
			try {
				if (cos != null) {
					cos.close();
				}
				if (fis != null) {
					fis.close();
				}
			}
			catch (Exception e) {
				// 释放资源出错，不将此类异常上抛.
				log.warn("关闭打开的文件流出错.", e);
			}
		}
	}

	/**
	 * 对文件进行解密处理
	 *
	 * @param key 密钥
	 * @param srcFile 要解密的文件
	 * @param destFile 解密后的文件
	 * @throws Exception 文件解密过程中产生的异常信息
	 */
	public static void decryptFile(byte[] key, String srcFile, String destFile) {
		// 加密文件输入流.
		CipherInputStream cis = null;

		// 解密后的文件输出流.
		FileOutputStream fos = null;

		try {
			// 构建密钥对象，采用预置的加密算法，密钥模式为解密模式.
			Cipher cipher = getCipher(processKey(key), ALGORITHM, Cipher.DECRYPT_MODE);
			fos = new FileOutputStream(destFile);

			// 构建加密文件输入流.
			cis = new CipherInputStream(new FileInputStream(srcFile), cipher);

			// 将解密后的数据写入解密文件流.
			byte[] b = new byte[BUFFER_SIZE];
			int len = -1;
			while ((len = cis.read(b)) != -1) {
				fos.write(b, 0, len);
			}
			fos.flush();
		}
		catch (Exception e) {
			// 异常处理，将异常转换成业务类异常抛出
			String errCode = GlobalExceptionDatas.COMM_ERR_DECRYPT_FILE;
			throw new GlobalException(errCode, srcFile, destFile);
		}
		finally {
			try {
				// 对打开的文件资源进行释操作.
				if (fos != null) {
					fos.close();
				}
				if (cis != null) {
					cis.close();
				}
			}
			catch (Exception e) {
				// 释放资源出错，不将此类异常上抛.
				log.warn("关闭打开的文件流出错.", e);
			}
		}
	}

	/**
	 * 对指定的数据进行MD5摘要算法，返回MD5后的十六进制字符串.
	 *
	 * @param data 要进行MD5加密的数据.
	 * @return 加密后的数据.
	 * @throws Exception 进行MD5转码过程产生的异常信息.
	 */
	public static String md5(String data) {
		// 执行MD5加密后的数据
		byte[] tmpData = md5(data.getBytes());

		// 返回转换成十六进制的字符串.
		return new String(Hex.encodeHex(tmpData));
	}

	/**
	 * 对指定的数据进行MD5摘要算法，返回MD5后的十六进制字符串.
	 *
	 * @param data 要进行MD5加密的数据.
	 * @return 加密后的数据.
	 * @throws Exception 进行MD5转码过程产生的异常信息.
	 */
	public static byte[] md5(byte[] data) {
		try {
			// 获取MD5摘要算法.
			MessageDigest messageDigest = MessageDigest.getInstance(MD5, PROVIDER_NAME);

			// 执行编码处理，返回编码后的数据
			return messageDigest.digest(data);
		}
		catch (Exception e) {
			// 异常处理，将加密异常转换成业务类异常.
			String errCode = GlobalExceptionDatas.COMM_ERR_MD5_ECRYPT;
			throw new GlobalException(errCode);
		}
	}

	/**
	 * 执行加密处理，并将加密后的数据转换BASE64格式的字符串.
	 *
	 * @param key 加密密钥
	 * @param data 要加密的数据.
	 * @return 加密后的数据.
	 * @throws GlobalException 加密过程中产生的异常信息.
	 */
	public static String encrypt(String key, String data) {
		try {
			// 将密钥字符串转换成byte数组
			byte[] keyByte = key.getBytes();

			// 用加密密钥对返回的数据进行加密.
			byte[] res =
					EncryptUtils.encrypt(keyByte, data.getBytes(SysConstants.DEFAULT_ENCODING));

			// 对返回的数据进行BASE64转码.
			byte[] dataByte = Base64.encodeBase64(res);

			// 返回数据.
			return new String(dataByte);
		}
		catch (Exception e) {
			// 异常处理，将异常转换成业务类异常抛出
			String errCode = GlobalExceptionDatas.COMM_ERR_ECRYPT;
			throw new GlobalException(errCode);
		}

	}

	/**
	 * 执行解密处理，解密的字符串为BASE64转码后的字符串，解密之前先将其进行BASE64转码.
	 *
	 * @param key 解密密钥.
	 * @param data 要处理的数据.
	 * @return 解密后的数据.
	 * @throws GlobalException 解密过程中产生的异常信息.
	 */
	public static String decrypt(String key, String data) {
		try {
			// 将密钥字符串转换成byte数组
			byte[] keyByte = key.getBytes();

			// 对请求的数据进行BASE64解码.
			byte[] dataByte = Base64.decodeBase64(data.getBytes());

			// 用加密密钥对请求的数据进行解密.
			byte[] res = EncryptUtils.decrypt(keyByte, dataByte);

			// 返回请求的内容.
			return new String(res, SysConstants.DEFAULT_ENCODING);
		}
		catch (Exception e) {
			// 异常处理，将异常转换成业务类异常抛出
			String errCode = GlobalExceptionDatas.COMM_ERR_DECRYPT;
			throw new GlobalException(errCode);
		}
	}
}
